from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from django.core.files.uploadedfile import InMemoryUploadedFile
from rest_framework.exceptions import APIException
from django.conf import settings
from pathlib import Path
from datetime import datetime
from utils.exceptions import HugeFile
import uuid
import tempfile
import zipfile
import base64
import requests


@api_view(['post'])
@permission_classes([IsAuthenticated])  # 至少是认证了的用户
def upload(request, app='default'):
    file_obj: InMemoryUploadedFile = request.data.get('file')

    if not file_obj:
        return Response({"error": "No file uploaded"}, status=400)

    if file_obj.size > 4096:
        raise HugeFile

    return FileUpload(app=app).handler(file_obj)


class FileUpload:
    def __init__(self, app: str = 'default'):
        self.app = app
        self.upload_map = {
            "default": self.ssh_key_upload,
            "iac": self.iac_upload,
        }

    def handler(self, file_obj):
        handler = self.upload_map.get(self.app)
        if not handler:
            return Response({"error": f"Unsupported app: {self.app}"}, status=400)
        return handler(file_obj)

    def ssh_key_upload(self, file_obj: InMemoryUploadedFile):
        uploads_base_dir = settings.JUMP_UPLOADS_DIR
        parent_dir = Path(f"{datetime.now():%Y/%m/%d}")
        (uploads_base_dir / parent_dir).mkdir(parents=True, exist_ok=True)
        filename = uuid.uuid4().hex
        sub_dir = Path(parent_dir / filename)

        with open(uploads_base_dir / sub_dir, 'wb+') as f:
            for c in file_obj.chunks():  # 迭代器
                f.write(c)

        return Response({"name": file_obj.name, "url": str(sub_dir)})  # 不需要存储base路径，因为可能会变

    def check_ridh_dir(self, dir) -> bool:
        """最基本要求：当前目录下，inventory/hosts必须存在，projects/site.yaml或yml必须有一个"""
        base_dir = Path(dir)
        if not Path(base_dir, "inventory/hosts").exists():
            return False
        if Path(base_dir, 'project/site.yaml').exists() or Path(base_dir, 'project/site.yml').exists():
            return True
        return False

    def git_file_upload(self, url_prefix, username, repo_name, project_name, filename="", token="", src="."):
        """假设已经解压了文件并且对RIDH目录做了基本的检查，现在把整个目录打包压缩，提交到Git仓库服务器，私有仓库需要token
        url例子：http://192.168.142.1:3000/api/v1/repos/wayne/test/contents/ridh1/Temp.zip
        """
        src = Path(src)
        if not src.is_dir():
            raise FileNotFoundError("目录不存在")
        if not filename:  # 如果未提供 filename，则自动生成一个临时文件名
            filename = Path(tempfile.mktemp(".zip", "playbook_")).name

        # 如果以 / 或 \ 结尾，去掉最后一个字符（避免重复斜杠）
        url = "/".join(map(lambda item: item[:-1] if item.endswith("/") or item.endswith("\\") else item,
                           (url_prefix, username, repo_name, "contents", project_name, filename)))
        print(url)
        # url =f"http://192.168.142.1:3000/api/v1/repos/{username}/{repo_name}/contents/{filepath}"

        dir = None  # 可指定在哪个目录下生成生成临时文件
        temp_dir = Path(tempfile.mkdtemp(dir=dir))  # 创建临时目录
        temp_filename = Path(temp_dir, filename)  # 临时目录下文件

        # 创建压缩文件
        with zipfile.ZipFile(temp_filename, 'w', zipfile.ZIP_DEFLATED) as zf:
            for p in src.glob('**/*'):  # 遍历源目录所有文件
                print(p.absolute(), p.relative_to(src))
                zf.write(
                    p.absolute(),  # 文件或目录
                    p.relative_to(src)  # 相对于src，zip文件内的路径
                )

        content = ""

        # git上传要求base64编码
        with open(temp_filename, 'rb') as f:
            data = f.read()
            print(type(data), len(data))
            buffer_b64 = base64.standard_b64encode(data)
            content = buffer_b64.decode()
            print(content, len(content))

        # 最简单的上传json，必须有content
        data = {"content": content}  # base64 编码 string
        print(data)
        headers = {"Authorization": f"token {token}"} if token else {}
        response = requests.post(url, json=data, headers=headers)
        if response.status_code == 201:  # 成功
            return response.json()
        else:  # 403 404 422 423
            raise APIException("上传文件失败")

    def iac_upload(self, file_obj: InMemoryUploadedFile):
        # 创建一个临时目录，用于zip解压缩
        dir = Path(tempfile.mkdtemp())
        try:
            with zipfile.ZipFile(file_obj) as zf:
                zf.extractall(dir)  # 解压到指定目录
            if not self.check_ridh_dir(dir):  # RIDH检查
                return Response({'result': "ridh文件格式错误"}, status=400)
            response = self.git_file_upload(src=str(dir.absolute()), **settings.REPO)
            return Response({'url': response['content']['url']})
        except Exception as e:
            print(e)
            print('+' * 30)
            return Response({'result': str(e)}, status=400)


def git_file_download(url, token):
    """
    从Git仓库服务器下载文件，直接使用url或raw url，私有仓库需要token，返回RIDH目录字符串
    url例子：
    http://192.168.142.1:3000/api/v1/repos/wayne/test/contents/test6pro10/playbook_4wuu0nep.zip
    http://192.168.142.1:3000/api/v1/repos/wayne/test/contents/test6pro10/playbook_4wuu0nep.zip?ref=main
    """
    headers = {"Authorization": f"token {token}"} if token else {}
    response = requests.get(url, headers=headers)  # 200或404
    if response.status_code != 200:
        raise FileNotFoundError("文件不存在")
    print(response.status_code, response.encoding, response.headers.get("content-type"))
    json_content = response.json()  # dict
    if int(json_content.get('size', 0)) and json_content.get('encoding', '') == 'base64':
        b = base64.standard_b64decode(json_content.get('content'))  # 解码为原始的二进制数据
        print(type(b), len(b))  # bytes
        dir = tempfile.mkdtemp()  # 创建一个临时目录
        Path(dir).mkdir(parents=True, exist_ok=True)
        fd, name = tempfile.mkstemp(suffix=".zip", dir=dir)  # 指定目录创建一个临时的 .zip 文件
        with open(fd, "wb+") as f:
            f.write(b)
            f.flush()  # 强制将缓冲区的数据写入磁盘
            f.seek(0)
            with zipfile.ZipFile(f) as zf:
                zf.extractall(dir)  # 解压缩
            # shutil.rmtree(dir)
            return dir
    # 异常：文件不存在、文件大小不对、base64解码失败、解压缩失败、文件写入失败
    raise FileNotFoundError("文件不存在")
